#pragma once
#include <iostream>
#include <sys/select.h>
#include "mysocket.hpp"

using namespace std;

class SelectServer
{
    static const int FD_NUM_MAX = sizeof(fd_set) * 8; // select可以同时监控的最大套接字数量
    static const int DEF_FD = -1;
    Socket _listensock;
    uint16_t _port;
    string _ip;
    int _fdarr[FD_NUM_MAX]; // 用于存储建立好链接的套接字的文件描述符

public:
    SelectServer(uint16_t port, const string ip = "0.0.0.0")
        : _port(port),
          _ip(ip)
    {
        _fdarr[0] = _listensock.getsock();
        for (int i = 1; i < FD_NUM_MAX; ++i)
        {
            _fdarr[i] = DEF_FD; // 将数组中的文件描述符都初始化为-1
        }
    }

    void Init()
    {
        _listensock.Bind(_ip, _port);
        _listensock.Listen();
        // 设置端口号复用，防止因为TIME_WAIT状态导致的bind error
        int opt = 1;                                                                                   //!!!
        setsockopt(_listensock.getsock(), SOL_SOCKET, SO_REUSEPORT | SO_REUSEADDR, &opt, sizeof(opt)); 
    }

    void Run()
    {
        while (true)
        {
            // select监控指定套接字上的数据是否就绪
            // 每次循环select中的nfds、fd_set和timeout都要重新设置一次
            int maxfd = _fdarr[0];
            fd_set rfds;
            FD_ZERO(&rfds); // 一定记得要先清空
            for (int i = 0; i < FD_NUM_MAX; ++i)
            {
                if (_fdarr[i] != DEF_FD)
                {
                    FD_SET(_fdarr[i], &rfds);
                    if (_fdarr[i] > maxfd)
                        maxfd = _fdarr[i];
                }
            }
            // cout << "maxfd: " << maxfd << endl;
            // timeval timeout = {5, 0};
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); //最多等待5s后返回
            if (n == 0) // 没有文件描述符就绪
            {
                cout << "timeout!" << endl;
                // do other thing...
            }
            else if (n > 0) // 有n个文件描述符就绪
            {
                // cout << n << " fd ready, remaining time: " << timeout.tv_sec << ":" << timeout.tv_usec << endl;
                Dispatcher(rfds); // 将就绪的套接字分派给连接管理器或通信服务器
            }
            else //发生错误
            {
                cout << "select error!" << endl;
            }
        }
    }

    ~SelectServer()
    {
        for (int i = 0; i < FD_NUM_MAX; ++i)
        {
            if (_fdarr[i] != DEF_FD)
                close(_fdarr[i]);
        }
    }

private:
    void Dispatcher(const fd_set &rfds)
    {
        if (FD_ISSET(_fdarr[0], &rfds)) // 监听套接字就绪，可以直接accept获取新链接
        {
            Acceptor();
        }
        for (int i = 1; i < FD_NUM_MAX; ++i) // 其他套接字就绪，可以直接recv读取并处理数据
        {
            if (_fdarr[i] != DEF_FD && FD_ISSET(_fdarr[i], &rfds))
            {
                Receiver(_fdarr[i], i);
            }
        }
    }

    void Acceptor()
    {
        string ip;
        uint16_t port;
        int fd = _listensock.Accept(&ip, &port);
        if (fd == -1)
            return;
        cout << "get a new link! " << ip << "/" << port << endl;
        int i = 0;
        for (; i < FD_NUM_MAX; ++i)
        {
            if (_fdarr[i] == DEF_FD)
                break;
        }
        if (i == FD_NUM_MAX) //超出select的最大监听数量，只能关闭
        {
            cout << "server is full, close fd" << fd << endl;
            close(fd);
            return;
        }
        else
        {
            cout << "add " << fd << "fd in the fd_set" << endl;
            _fdarr[i] = fd;
        }
    }

    void Receiver(int fd, int pos)
    {
        char buffer[1024];
        int sz = recv(fd, &buffer, sizeof(buffer) - 1, 0);
        if (sz > 0)
        {
            buffer[sz] = 0;
            cout << "get a message! echo: " << buffer << endl;
        }
        else if (sz == 0)
        {
            cout << "client quit, service end. close fd" << fd << endl;
            close(fd); 
            _fdarr[pos] = DEF_FD; // 不仅要将套接字关闭，还要将_fdarr中的记录删除，使下一轮不再监听该套接字
        }
        else
        {
            perror("recv");
            exit(errno);
        }
    }
};